
/*
	(c) Galustyan Sergey 2009
*/

#include "dxut.h"
#include "SDKmisc.h"
#include "dxutres.h"
#include "Lib.h"

namespace TestTask
{
	#define setIdentity D3DXMatrixIdentity
	
	hres createSphere( float radius, PDevice device, PMesh& sphere )
	{
		scomptr<ID3DXBuffer> adj;
		D3DXCreateSphere( device, radius, 30, 30, &sphere, &adj );
		checked( D3DXComputeNormals( sphere, (DWORD*)adj->GetBufferPointer()) )
		return S_OK;
	}

	hres createArrow( PDevice device, PMesh& mesh)
	{
		return DXUTCreateArrowMeshFromInternalArray( device, &mesh );
	}

	bool intersectsRay( Vector3 orig, Vector3 dir, Vector3 boxmin, Vector3 boxmax, Vector3& point) 
	{
		//http://code.google.com/p/toxiclibs/source/browse/trunk/toxiclibs/src.core/toxi/geom/AABB.java
		Vector3 invDir = Vector3( 1.0f / dir.x, 1.0f / dir.y, 1.0f / dir.z);
        bool signDirX = invDir.x < 0;
        bool signDirY = invDir.y < 0;
        bool signDirZ = invDir.z < 0;
        Vector3 bbox = signDirX ? boxmax : boxmin;
        float tmin = (bbox.x - orig.x) * invDir.x;
        bbox = signDirX ? boxmin : boxmax;
        float tmax = (bbox.x - orig.x) * invDir.x;
        bbox = signDirY ? boxmax : boxmin;
        float tymin = (bbox.y - orig.y) * invDir.y;
        bbox = signDirY ? boxmin : boxmax;
        float tymax = (bbox.y - orig.y) * invDir.y;

        if ((tmin > tymax) || (tymin > tmax)) return false;
        if (tymin > tmin) tmin = tymin;
        if (tymax < tmax) tmax = tymax;

        bbox = signDirZ ? boxmax : boxmin;
        float tzmin = (bbox.z - orig.z) * invDir.z;
        bbox = signDirZ ? boxmin : boxmax;
        float tzmax = (bbox.z - orig.z) * invDir.z;

        if ((tmin > tzmax) || (tzmin > tmax)) return false;
        if (tzmin > tmin) tmin = tzmin;
        if (tzmax < tmax) tmax = tzmax;
        
		point = orig + dir*tmin;
        return true;
	}

	void getRayFromCamera( Vector3* origin, Vector3* direction, Matrix& world, Matrix& view, Matrix& proj )
	{
	    const D3DSURFACE_DESC* pd3dsdBackBuffer = DXUTGetD3D9BackBufferSurfaceDesc();

		POINT ptCursor;
		GetCursorPos( &ptCursor );
		ScreenToClient( DXUTGetHWND(), &ptCursor );

		Vector3 v;
		v.x = ( ( ( 2.0f * ptCursor.x ) / pd3dsdBackBuffer->Width ) - 1 ) / proj._11;
		v.y = -( ( ( 2.0f * ptCursor.y ) / pd3dsdBackBuffer->Height ) - 1 ) / proj._22;
		v.z = 1.0f;

		D3DXMATRIX mWorldView = world * view;
		D3DXMATRIX m;
		D3DXMatrixInverse( &m, NULL, &mWorldView );

		direction->x = v.x * m._11 + v.y * m._21 + v.z * m._31;
		direction->y = v.x * m._12 + v.y * m._22 + v.z * m._32;
		direction->z = v.x * m._13 + v.y * m._23 + v.z * m._33;
		origin->x = m._41;
		origin->y = m._42;
		origin->z = m._43;
	}

	void computeAABB( const PMesh& mesh, Vector3& box_min, Vector3& box_max )
	{
		Vector3* data;
		mesh->LockVertexBuffer(0, (void**)&data);
		D3DXComputeBoundingBox( data, mesh->GetNumVertices(), D3DXGetFVFVertexSize( mesh->GetFVF() ), &box_min, &box_max );
		mesh->UnlockVertexBuffer();
	}
	

		// vl1-vl2 must be from 0 to 10
	// 0 <= vl <= 10, pack for angles
	void float_to_float2( float vl, float& x1, float& x2 )
	{
		vl = vl*25;
		x2 = floor(vl)/256;
		vl = (vl - floor(vl))*256;
		x1 = floor(vl)/256;
	}

	float float2_to_float( float& x1, float& x2 )
	{
		return (x2*256 + x1/256)/25;
	}

	
	D3DXCOLOR float2_to_color( float vl1, float vl2 )
	{
		float x1,x2,x3,x4;
		float_to_float2( vl1, x1, x2 );
		float_to_float2( vl2, x3, x4 );
		return D3DXCOLOR( x1, x2, x3, x4 );
	}

	void color_to_2float( D3DXCOLOR cl, float& vl1, float& vl2 )
	{
		vl1 = float2_to_float( cl.r, cl.g );
		vl2 = float2_to_float( cl.b, cl.a );
	}
	
	D3DXCOLOR float_to_color( float vl )
	{
		float x4 = floor(vl/256)/256;
		vl = (vl - floor(vl/256)*256);
		float x3 = floor(vl)/256;
		vl = (vl - floor(vl))*256.0f;
		float x2 = floor(vl)/256;
		vl = (vl - floor(vl))*256.0f;
		float x1 = floor(vl)/256;

		return D3DXCOLOR(x1, x2, x3, x4);
	}

	float color_to_float(D3DXCOLOR color)
	{
		return (1.0f/256.0f)*color.r + color.g + 256.0f*color.b + 256.0f*256.0f*color.a;
	}

	Vector3 norm( const Vector3& src )
	{
		Vector3 res;
		D3DXVec3Normalize( &res, &src );
		return res;
	}

	Vector3 perpen( const Vector3& a )
	{
		Vector3 result;
		if ( abs(a.x) > 0.001f ) result = norm( Vector3( -(a.y+a.z)/a.x, 1, 1 ) );
		else if ( abs(a.y) > 0.001f ) result = norm( Vector3( 1, -(a.x+a.z)/a.y, 1) );
		else if ( abs(a.z) > 0.001f ) result = norm( Vector3( 1, 1, -(a.x+a.y)/a.z ) );
		return result;
	}

	void forward_axis( const Vector3& base, Vector3& up, Vector3& right )
	{
		up = perpen( base );
		Vector3 r;
		D3DXVec3Cross( &r, &base, &up );
		D3DXVec3Normalize( &right, &r );
	}

	Vector3 sphere_point( const Vector3& center, 
					   float radius,
					   const Vector3& ax, 
					   const Vector3& ay, 
					   const Vector3& az, 
					   float an1, float an2 )
	{
		return center + ax*radius*sin(an1)*cos(an2) +
						ay*radius*sin(an1)*sin(an2) +
						az*radius*cos(an1);
	}

	namespace
	{
		struct ScreenQuadVertex
		{
			FLOAT x, y, z, rhw; 
			FLOAT u,v;        
			static const int fvf = (D3DFVF_XYZRHW|D3DFVF_TEX1);
		};
	}
		
	hres   createScreenQuad( PDevice device, int frameWidth, int frameHeight, PVertexBuffer& vertexBuffer )
	{
		// make full buffer quad
		// using -0.5f to perform direct pixel-texel mapping
		Vector3 p1 = Vector3( -0.5f, - 0.5f, 0.5f );
		Vector3 p2 = Vector3( -0.5f, frameWidth - 0.5f, 0.5f );
		Vector3 p3 = Vector3( frameHeight - 0.5f, - 0.5f, 0.5f );
		Vector3 p4 = Vector3( frameWidth - 0.5f, frameWidth - 0.5f, 0.5f );
		
		ScreenQuadVertex vertices[] =
		{
			{ p1.x,  p1.y, p1.z, 1.0f, 0, 1 },
			{ p3.x,  p3.y, p3.z, 1.0f, 1, 1 },
			{ p2.x,  p2.y, p2.z, 1.0f, 0, 0 },
			
			{ p2.x,  p2.y, p2.z, 1.0f, 0, 0 },
			{ p3.x,  p3.y, p3.z, 1.0f, 1, 1 },
			{ p4.x,  p4.y, p4.z, 1.0f, 1, 0 },
		};

		createVertexBuffer<ScreenQuadVertex>( device, 6, vertexBuffer );
		writeVertexBuffer<ScreenQuadVertex>( vertices, 6, vertexBuffer );
		
		return S_OK;
	}

	hres   renderScreenQuad( PDevice device, PVertexBuffer& screenQuad)
	{
		return renderVertexBuffer<ScreenQuadVertex>( device, screenQuad, D3DPT_TRIANGLELIST, 0, 2 );
	}

	// ---------

	RenderObject::RenderObject(Vector3 position, 
							   Color diffuse, 
							   XHandle renderTech):
		diffuse(diffuse),
		master(0),
		position(position),
		subsetCount(1),
		renderTech(renderTech), setWorld( "world" ), setWorldViewProj ( "worldViewProjection" ), setDiffuseColor( "diffuse" )
	{
		setIdentity( &worldMatrix );
		setIdentity( &rotation );
		setIdentity( &scale );
		ApplyTransform();
	}

	RenderObject* RenderObject::Append(PRenderObject child)
	{
		childs.push_back(child);
		child->master = this;
		return this;
	}
	
	void RenderObject::Render( RenderPackage& rp )
	{
		PEffect effect = rp.effect;
		Matrix wvp = worldMatrix*rp.viewProj;

		setWorld( effect, worldMatrix );
		setWorldViewProj( effect, wvp );
		setDiffuseColor( effect, diffuse );

		effect->CommitChanges();

		if (mesh)
			for (UINT subset = 0; subset < subsetCount; ++subset)
				mesh->DrawSubset(subset);
	}

	hres RenderObject::RenderWithEffect( RenderPackage& rp )
	{
		PEffect effect = rp.effect;
		effect->SetTechnique( renderTech );
		UINT cPasses;
		effect->Begin( &cPasses, 0 );
		for ( UINT iPass = 0; iPass < cPasses; ++iPass )
		{
			effect->BeginPass( iPass );
			Render( rp );
			effect->EndPass();
		} 
		effect->End();
		return S_OK;
	}		

	hres RenderObject::RenderMain( RenderPackage& rp )
	{
		RenderWithEffect( rp );
		foreach(PRenderObject obj, childs) obj->RenderMain( rp );
		return S_OK;
	}

	const Vector3& RenderObject::GetPosition()
	{
		return position;
	}

	void RenderObject::SetPosition( const Vector3& pos )
	{
		position = pos;
	}

	void RenderObject::SetScale( float v )
	{
		D3DXMatrixScaling( &scale, v, v, v );
	}

	void RenderObject::Move( Vector3& offset )
	{
		position += offset;
		ApplyTransform();
	}

	void RenderObject::ApplyTransform()
	{
		setIdentity( &worldMatrix );
		D3DXMatrixTranslation(&worldMatrix,position.x,position.y,position.z);
		worldMatrix = scale*rotation*worldMatrix;
		
		if ( master ) worldMatrix = worldMatrix*master->worldMatrix;

		foreach( PRenderObject obj, childs ) obj->ApplyTransform();
	}

	// ------

	hres createTexture( PDevice device, UINT width, UINT height, DWORD Usage, D3DFORMAT Format, PTexture& texture )
	{
		return device->CreateTexture( width, height, 0, Usage, Format, D3DPOOL_DEFAULT, &texture, 0);
	}

	hres writeTexture( PTexture texture, void* data, UINT size )
	{
		D3DLOCKED_RECT locked;
		checked(texture->LockRect(0,&locked,NULL,D3DLOCK_DISCARD))
		memcpy(locked.pBits, data, size);
		return texture->UnlockRect(0);
	}

	// ------------

	XHandle SetEffectParameter::init(PEffect effect){
		foreach(Desc& kv, descs) if ( kv.first == effect ) return kv.second; 
		descs.push_back( std::make_pair( effect, effect->GetParameterByName( NULL, name ) ) );
		return descs.rbegin()->second;
	}
}